/*
 * 代号：凤凰
 * http://www.jphenix.org
 * 2024年7月4日
 * V4.0
 */
package com.jphenix.service.db.util;

import java.io.BufferedReader;
import java.io.CharArrayWriter;
import java.io.StringReader;
import java.sql.CallableStatement;
import java.sql.Clob;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Types;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import com.jphenix.share.util.BaseUtil;
import com.jphenix.share.util.DebugUtil;
import com.jphenix.standard.db.QueryPageVO;
import com.jphenix.standard.docs.ClassInfo;
import com.jphenix.standard.log.ILog;
import com.jphenix.share.lang.SDouble;
import com.jphenix.share.lang.SInteger;
import com.jphenix.share.lang.SString;

/**
 * 数据库操作工具类
 * com.jphenix.service.db.util.DBUtil
 * 
 * 2024-07-10 新增迁移方法fixSqlInfo
 * 
 * @author MBG
 * 2024年7月4日
 */
@ClassInfo({"2024-07-10 23:10","数据库操作工具类"})
public class DBUtil {

	//#region getClobString 获取Clob值
	/**
	 * 获取Clob值
	 * 刘虻
	 * 2011-6-2 下午03:43:05
	 * @param clob        Clob对象
	 * @return            Clob值
	 * @throws Exception  异常
	 */
	public static String getClobString(Clob clob) throws Exception {
		if (clob==null) {
			return null;
		}
		//构造字节输出流
		CharArrayWriter caw = new CharArrayWriter();
		//获取输入流
		BufferedReader inBuf = new BufferedReader(clob.getCharacterStream());
		int readByte = 0; //每次读取的字节数
		while(true) {
			//读取一个字节
			readByte = inBuf.read();
			if (readByte<0) {
				break;
			}
			//放入缓存
			caw.write(readByte);
		}
		return caw.toString();
	}
	//#endregion

	//#region fixSqlInfo 整理传入信息（用于复杂语句拼装接入信息处理）
	/**
	 * 整理传入信息（用于复杂语句拼装接入信息处理）
	 * 注意：判断提交值是否为空这个动作，在ScriptUtil.fixSqlSub 方法中已经做过了
	 * @param log   日志处理类
	 * @param infos 待处理信息
	 * @return 处理后信息  [0] String sql语句         [1] List<String> 提交值数组
	 * 2017年1月22日
	 * @author MBG
	 */
	@SuppressWarnings("unchecked")
	public static Object[] fixSqlInfo(ILog log,Object[] infos) {
		//构建返回值
		Object[] reObjs = new Object[2];
		if(infos==null || infos.length<1) {
			reObjs[0] = "";
			reObjs[1] = null;
			return reObjs;
		}
		//构建拼装语句
		StringBuffer sqlSbf = new StringBuffer();
		//构架提交值序列
		List<String> paraList = new ArrayList<String>();
		for(int i=0;i<infos.length;i++) {
			if(infos[i]==null) {
				continue;
			}
			if(infos[i] instanceof String) {
				sqlSbf.append(infos[i]);
			}else if(infos[i] instanceof Object[]){
				//通过 ScriptUtil.fixSqlSub() 方法处理后得到的值
				String sqlSub = SString.valueOf(((Object[])infos[i])[0]);
				sqlSbf.append(sqlSub);
				if(((Object[])infos[i])[1]!=null) {
					if(((Object[])infos[i])[1] instanceof List) {
						//在使用拼装in(?,?,?) 语句时提交的序列
						paraList.addAll((List<String>)((Object[])infos[i])[1]);
					}else {
						paraList.add((String)((Object[])infos[i])[1]);
					}
				}else if(sqlSub.indexOf("?")>-1) {
					//如果提交的语句段中存在需要设置值得标记，提交空字符串
					paraList.add("");
				}
			}else {
				log.warning("Fix Sql Infos Error:"+DebugUtil.getArrayString(infos)+"\n ErrorSubf:["+infos[i]+"]",null);
				continue;
			}
		}
		reObjs[0] = sqlSbf.toString();
		reObjs[1] = paraList;
		return reObjs;
	}
	//#endregion

	//#region fixSqlWithPara 拼装语句时，检查提交值，如果提交值为空或空字符串，就去掉某段语句
	/**
	 * 拼装语句时，检查提交值，如果提交值为空或空字符串，就去掉某段语句
	 * 
	 * 只有以 (#) 开头的语句，用来处理提交值信息  
	 * 
	 * (#)select id,name from table where 1=1 (#) and name=? (#) and id=? (#) and dr=0
	 * 
	 * 注意： 条件值判断处理结束的位置，也需要加上分隔符，比如 and dr=0 前面，要加上分隔符，否则后面的值不一定会显示出来
	 * 
	 * @param sql          带处理的语句
	 * @param uList        提交值（注意：执行该方法后，其内部的值也会发生变化）
	 * @return 处理后的语句
	 * 2015年5月8日
	 * @author 马宝刚
	 */
	public static String fixSqlWithPara(String sql,List<String> uList) {
		if(sql==null || uList==null) {
			//不符合筛选条件
			return sql;
		}
		sql = sql.trim(); //待处理的SQL语句
		if(!sql.startsWith("(#)")) {
			return sql;
		}
		//去掉标识符
		sql = sql.substring(3);
		//构造返回值
		StringBuffer newSql = new StringBuffer();
		//分割语句段
		List<String> sqlList = BaseUtil.splitToList(sql,"(#)");
		if(sqlList.size()<2) {
			return sql;
		}
		newSql.append(sqlList.get(0)); //添加第0个位置

		int sIndex = 1;  //语句索引
		int uIndex = 0; //提交值索引

		//如果分割标识（除去第一个以外）的数量小于提交值的数量
		//程序认为提交值中，头几个值不做为空判断，凡是做为空判断
		//的值都在放后面
		if(sqlList.size()-1<uList.size()) {
			uIndex = uList.size()-sqlList.size()+1;
		}

		/*
		 * 注意，sql语句分割后，从第一个元素（非第0个元素）开始
		 * 也就是说，sql分割后，第一个元素对应提交值的第0个元素
		 * 
		 * sqlSub 的作用，除了开头和末尾需要加(#) 中间每段语句都要有? ，跟序列中的值一一对应
		 * 但是复杂的sql语句中， 带?的语句段之间可能要有不带?的语句段，sqlSub就是处理不带问号
		 * 的语句段，遇到不带问好的语句段，跳过这段
		 */
		String sqlSub; //语句段
		Object uEle; //提交值元素
		while(uList.size()>0 && uIndex<uList.size() && sIndex<sqlList.size()) {
			sqlSub = SString.valueOf(sqlList.get(sIndex));
			while(sqlSub.indexOf("?")<0) {
				//末尾段
				newSql.append(sqlSub);
				sIndex++;
				continue;
			}
			//处理判断提交值
			uEle = uList.get(uIndex);
			if(uEle==null || uEle.toString().length()<1 || "%".equals(uEle) || "%%".equals(uEle)) {
				sIndex++;
				uList.remove(uIndex);
				continue;
			}
			newSql.append(sqlList.get(sIndex));
			sIndex++;
			uIndex++;
		}
		for(int i=sIndex;i<sqlList.size();i++) {
			newSql.append(sqlList.get(i));
		}
		return newSql.toString();
	}
	//#endregion
	
	//#region fixSqlWithPara 拼装语句时，检查提交值，如果提交值为空或空字符串，就去掉某段语句
	/**
	 * 拼装语句时，检查提交值，如果提交值为空或空字符串，就去掉某段语句
	 * 
	 * 只有以 (#) 开头的语句，用来处理提交值信息  
	 * 
	 * (#)select id,name from table where 1=1 (#) and name=? (#) and id=? (#) and dr=0
	 * 
	 * 注意： 条件值判断处理结束的位置，也需要加上分隔符，比如 and dr=0 前面，要加上分隔符，否则后面的值不一定会显示出来
	 * 
	 * @param sql          带处理的语句
	 * @param uList        提交值（注意：执行该方法后，其内部的值也会发生变化）
	 * @return 处理后的信息 0语句 1提交值
	 * 2015年5月8日
	 * @author 马宝刚
	 */
	public static Object[] fixSqlWithPara(String sql,Object[] params) {
		//构建返回值
		Object[] res = new Object[2];
		res[0] = sql;
		res[1] = params;
		if(sql==null || params==null || params.length<1) {
			//不符合筛选条件
			return res;
		}
		sql = sql.trim(); //待处理的SQL语句
		if(!sql.startsWith("(#)")) {
			return res;
		}
		//去掉标识符
		sql = sql.substring(3);
		//构造返回值
		StringBuffer newSql = new StringBuffer();
		//分割语句段
		List<String> sqlList = BaseUtil.splitToList(sql,"(#)");
		if(sqlList.size()<2) {
			return res;
		}
		newSql.append(sqlList.get(0)); //添加第0个位置

		//处理后的提交值序列
		List<Object> fixParaList = new ArrayList<Object>();
		fixParaList.addAll(Arrays.asList(params));

		int sIndex = 1;  //语句索引
		int uIndex = 0; //提交值索引

		//如果分割标识（除去第一个以外）的数量小于提交值的数量
		//程序认为提交值中，头几个值不做为空判断，凡是做为空判断
		//的值都在放后面
		if(sqlList.size()-1<fixParaList.size()) {
			uIndex = fixParaList.size()-sqlList.size()+1;
		}

		/*
		 * 注意，sql语句分割后，从第一个元素（非第0个元素）开始
		 * 也就是说，sql分割后，第一个元素对应提交值的第0个元素
		 * 
		 * sqlSub 的作用，除了开头和末尾需要加(#) 中间每段语句都要有? ，跟序列中的值一一对应
		 * 但是复杂的sql语句中， 带?的语句段之间可能要有不带?的语句段，sqlSub就是处理不带问号
		 * 的语句段，遇到不带问好的语句段，跳过这段
		 */
		String sqlSub; //语句段
		Object uEle; //提交值元素
		while(fixParaList.size()>0 && uIndex<fixParaList.size() && sIndex<sqlList.size()) {
			sqlSub = SString.valueOf(sqlList.get(sIndex));
			while(sqlSub.indexOf("?")<0) {
				//末尾段
				newSql.append(sqlSub);
				sIndex++;
				continue;
			}
			//处理判断提交值
			uEle = fixParaList.get(uIndex);
			if(uEle==null || uEle.toString().length()<1 || "%".equals(uEle) || "%%".equals(uEle)) {
				sIndex++;
				fixParaList.remove(uIndex);
				continue;
			}
			newSql.append(sqlList.get(sIndex));
			sIndex++;
			uIndex++;
		}
		for(int i=sIndex;i<sqlList.size();i++) {
			newSql.append(sqlList.get(i));
		}
		res[0] = newSql.toString();
		res[1] = BaseUtil.listToArray(fixParaList,Object.class);
		return res;
	}
	//#endregion
	
	//#region fixSqlWithPara 拼装语句时，检查提交值，如果提交值为空或空字符串，就去掉某段语句
	/**
	 * 拼装语句时，检查提交值，如果提交值为空或空字符串，就去掉某段语句
	 * 
	 * 只有以 (#) 开头的语句，用来处理提交值信息  
	 * 
	 * (#)select id,name from table where 1=1 (#) and name=? (#) and id=? (#) and dr=0
	 * 
	 * 注意： 条件值判断处理结束的位置，也需要加上分隔符，比如 and dr=0 前面，要加上分隔符，否则后面的值不会显示出来
	 * 
	 * 注意： 如果语句尾部没有order by 之类的需要保留的语句段，末尾也要加上 (#) 
	 * 
	 * @param qpVO   查询信息对象，注意：sql语句和 countSql语句中的分割信息要保持一致，否则会操作错误
	 * 2015年5月8日
	 * @author 马宝刚
	 */
	@SuppressWarnings({ "rawtypes", "unchecked" })
	public static void fixSqlWithPara(QueryPageVO qpVO) {
		if(qpVO.sql==null || qpVO.uList==null) {
			//不符合筛选条件
			return;
		}
		qpVO.sql = qpVO.sql.trim();
		if(!qpVO.sql.startsWith("(#)")) {
			return;
		}
		//去掉标识符
		qpVO.sql = qpVO.sql.substring(3);

		if(qpVO.countSql!=null) {
			qpVO.countSql = qpVO.countSql.trim();
			if(qpVO.countSql.startsWith("(#)")) {
				qpVO.countSql = qpVO.countSql.substring(3);
			}
		}
		//构造新的查询语句
		StringBuffer newSql = new StringBuffer();
		//构造新的获取记录数语句
		StringBuffer newCountSql = new StringBuffer();
		//分割语句段
		List<String> sqlList = BaseUtil.splitToList(qpVO.sql,"(#)");
		//分割获取记录数语句段
		List<String> countSqlList = BaseUtil.splitToList(qpVO.countSql,"(#)");
		newSql.append(sqlList.get(0)); //添加第0个位置
		if(countSqlList.size()>0) {
			newCountSql.append(countSqlList.get(0));
		}
		int sIndex = 1;  //语句索引
		int uIndex = 0; //提交值索引
		List uList = new ArrayList(); //提交值序列
		if(qpVO.uList.size()>0) {
			uList.addAll(qpVO.uList);
		}else if(qpVO.uParas!=null) {
			for(Object ele:qpVO.uParas) {
				uList.add(ele);
			}
		}
		//如果分割标识（除去第一个和最后一个以外）的数量小于提交值的数量
		//程序认为提交值中，头几个值不做为空判断，凡是做为空判断
		//的值都在放后面
		if(sqlList.size()-2<uList.size()) {
			uIndex = uList.size()-sqlList.size()+2;
		}
		/*
		 * 注意，sql语句分割后，从第一个元素（非第0个元素）开始
		 * 也就是说，sql分割后，第一个元素对应提交值的第0个元素
		 * 
		 * sqlSub 的作用，除了开头和末尾需要加(#) 中间每段语句都要有? ，跟序列中的值一一对应
		 * 但是复杂的sql语句中， 带?的语句段之间可能要有不带?的语句段，sqlSub就是处理不带问号
		 * 的语句段，遇到不带问号的语句段，跳过这段
		 * 
		 */
		String sqlSub; //语句段
		String uEle;   //提交值元素
		while(uList.size()>0 && uIndex<uList.size() && sIndex<sqlList.size()) {
			sqlSub = SString.valueOf(sqlList.get(sIndex));
			while(sqlSub.indexOf("?")<0) {
				if(sqlList.size()>=sIndex+1) {
					//末尾段
					newSql.append(sqlSub);
					if(countSqlList.size()>sIndex) {
						newCountSql.append(countSqlList.get(sIndex));
					}
					qpVO.sql      = newSql.toString();
					qpVO.countSql = newCountSql.toString();
					return;
				}
				//中间的无问号语句段
				sIndex++;
				sqlSub = SString.valueOf(sqlList.get(sIndex));
			}
			//处理判断提交值
			uEle = SString.valueOf(uList.get(uIndex));
			if(uEle.length()<1 || "%".equals(uEle) || "%%".equals(uEle)) {
				sIndex++;
				uList.remove(uIndex);
				continue;
			}
			newSql.append(sqlList.get(sIndex));
			if(countSqlList.size()>sIndex) {
				newCountSql.append(countSqlList.get(sIndex));
			}
			sIndex++;
			uIndex++;
		}
		//补齐尾部语句段
		for(int i=sIndex;i<sqlList.size();i++) {
			newSql.append(sqlList.get(i));
			if(countSqlList.size()>i) {
				newCountSql.append(countSqlList.get(i));
			}
		}
		qpVO.sql      = newSql.toString();
		qpVO.countSql = newCountSql.toString();

		if(qpVO.uList.size()>0) {
			qpVO.uList = uList;
		}else if(qpVO.uParas!=null) {
			qpVO.uParas = uList.toArray();
		}
		return;
	}
	//#endregion

	//#region fixPreparedStatement 设置操作值
	/**
	 * 设置操作值
	 * 刘虻
	 * 2011-6-2 下午03:41:52
	 * @param stmt      事务对象
	 * @param valueList    操作值序列
	 * @return 插入值日志信息
	 * @throws Exception  异常
	 */
	public static String fixPreparedStatement(
			PreparedStatement stmt,List<String> uList) throws Exception {
		if(uList==null || uList.size()<1){
			return "";
		}
		//构建返回值
		StringBuffer reSbf = new StringBuffer();
		String value; //元素
		for(int i=0;i<uList.size();i++){
			value = uList.get(i);
			if(value==null){
				stmt.setNull(i+1, Types.VARCHAR);
				reSbf.append("+++Set Property"+(i+1)+":{NULL}\n");
			}else{
				stmt.setString(i+1, value);
				reSbf.append("+++Set Property"+(i+1)+":["+value+"]\n");
			}
		}
		return reSbf.toString();
	}
	//#endregion

	//#region fixPreparedStatement 将值放入事务中
	/**
	 * 将值放入事务中
	 * @author 刘虻
	 * @param stmt 事务
	 * @param fieldObjectArrl 值序列 
	 * @return 插入值日志信息
	 * 2006-3-30 19:44:40
	 * @throws SQLException 设置值时发生异常
	 */
	public static String fixPreparedStatement(
			PreparedStatement stmt,Object[] params,int[] types,boolean[] isOuts) throws SQLException {
		// 导入参数合法化（必要）
		if (stmt==null || params==null || types==null || isOuts==null
				|| params.length<1 || params.length!=types.length || params.length!=isOuts.length) {
			return "";
		}
		//构建返回值
		StringBuffer reSbf = new StringBuffer();
		String paraStr;
		for (int i=0;i<params.length;i++) {
			paraStr = (params==null)?"":params.toString();
			if(isOuts[i]) {
				reSbf.append("+++Register Out  Property"+(i+1)+":"+types[i]+"\n");
				//注册存储过程返回值
				((CallableStatement)stmt).registerOutParameter(i+1,types[i]);
			}else {
				//设置传入值
				if(types[i]==Types.VARCHAR) {
					if(params[i]==null) {
						reSbf.append("+++Set Property"+(i+1)+":{NULL}\n");
						stmt.setNull(i+1, Types.VARCHAR);
					}else {
						reSbf.append("+++Set Property"+(i+1)+":["+params[i]+"]\n");
						try {
							stmt.setString(i+1,paraStr);
						}catch(SQLException e) {
							//Oracle 大于2000个字节总是报错
							stmt.setCharacterStream(i+1,new StringReader(paraStr),paraStr.length());
						}
					}
				}else if(types[i]==Types.DOUBLE) {
					//转换为浮点型
					double doubleValue = 0;
					try {
						doubleValue = SDouble.valueOf(params[i]);
					}catch(Exception e) {
						e.printStackTrace();
						throw new SQLException("Fix Double Exception");
					}
					reSbf.append("Set Property "+(i+1)+":["+doubleValue+"]\n");
					//设置值
					stmt.setDouble(i+1,doubleValue);
				}else if(types[i]==Types.INTEGER) {
					reSbf.append("Set Property"+(i+1)+":["+SInteger.valueOf(params[i]+"]\n"));
					stmt.setInt(i+1, SInteger.valueOf(params[i]));
				}else if(types[i]==Types.FLOAT) {
					//转换为浮点型
					double doubleValue = 0;
					try {
						doubleValue = SDouble.valueOf(params[i]);
					}catch(Exception e) {
						e.printStackTrace();
						throw new SQLException("Fix Float Exception");
					}
					reSbf.append("Set Property "+(i+1)+":["+doubleValue+"]\n");
					//设置值
					stmt.setFloat(i+1,(float)doubleValue);
				}
			}
		}
		return reSbf.toString();
	}
	//#endregion
}
